package com.googlecode.YaccSyntaxTool.grammar;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import com.googlecode.YaccSyntaxTool.export.Exporter;
import com.googlecode.YaccSyntaxTool.export.NotSupportedExportKindException;
import com.googlecode.YaccSyntaxTool.export.ReferenceMap;

/**
 * This class represents the complete syntax in EBNF.
 *
 * @author goe
 *
 */
public class Syntax {

	/**
	 * Logger
	 */
	private final static Logger sLog = Logger.getLogger(Syntax.class.getName());

	private final String				title;
	private final Map<String,Rule>		rules;

	public Syntax(String title) {
		super();
		this.title = title;
		// use the order in which the rules were inserted into the map
		rules = new LinkedHashMap<String,Rule>();
	}

	public void addRule(Rule rule) {
		if (rule != null) {
			rules.put(rule.getNonterminal(),rule);
		}
	}

	public void addRule(String nonterminal, Expression expression, Set<String> excludes) {
		if (nonterminal != null
			&& !nonterminal.isEmpty()
			&& !excludes.contains(nonterminal) // do not add excluded rules
			&& expression != null) {
			addRule(new Rule(nonterminal,expression));
		}
	}

	/*
	 * EXPORT
	 */
	public void export(Exporter exporter) throws NotSupportedExportKindException {

		switch (exporter.getKind()) {
		case TXT:
			exporter.append(title).append(":\n");
			for (Rule rule : rules.values()) {
				rule.export(exporter);
			}
			break;
		case HTML:
			exporter.appendHtmlFile(title, Exporter.HTML_START_TAG);
			exporter.appendBold("Number of rules: ").append(rules.size());
			exporter.appendLineSeparator();
			for (Rule rule : rules.values()) {
				rule.export(exporter);
			}
			exporter.appendHtmlFile(null, Exporter.HTML_END_TAG);
			break;
		case HTMLCOMPACT:
			exporter.appendHtmlFile(title, Exporter.HTML_START_TAG);
			for (Rule rule : rules.values()) {
				rule.export(exporter);
			}
			exporter.appendHtmlFile(null, Exporter.HTML_END_TAG);
			break;
		default:
			throw new NotSupportedExportKindException(exporter.getKind());
		}
	}

	/*
	 * REFERENCES
	 */
	public void fillReferenceMap(ReferenceMap refMap, FactorType type) {

		for (Rule rule : rules.values()) {
			rule.fillReferenceMap(refMap,type);
		}
	}

	/*
	 * SIMPLIFY
	 */
	public void simplify() {

		for (Rule rule : rules.values()) {

			String nonterminal		= rule.getNonterminal();
			Expression expression	= rule.getExpression();

			expression.simplify();

			// TODO

			// GROUP

			// REPEAT
			if (expression.isRepeat(nonterminal)) {
			}

			// OPTION
		}
	}

	/*
	 * EXTRACT
	 */
	public Syntax extractSyntax(String title, String rootNonterminal) {
		Syntax extractedSyntax = new Syntax(title);

		// determine all used nonterminals
		Set<String> allUsedNonterminals = new LinkedHashSet<String>();

		fillUsedNonterminals(allUsedNonterminals, rootNonterminal);

		// add the root rule
		extractedSyntax.addRule(rules.get(rootNonterminal));

		// now add all other needed rules (use the correct dependency order of the LinkedHashSet!)
		for (String nonterminals : allUsedNonterminals) {
			// add rule to new syntax
			extractedSyntax.addRule(rules.get(nonterminals));
		}

		return extractedSyntax;
	}

	private void fillUsedNonterminals(Set<String> allUsedNonterminals, String nonterminal) {
		// get rule from this syntax
		Rule rule = rules.get(nonterminal);

		if (rule != null) {
			// determine all nonterminals, that are used in this rule
			Set<String> usedNonterminals = rule.getUsedNonterminals();

			// remove all already known nonterminals
			usedNonterminals.removeAll(allUsedNonterminals);

			// add nonterminals directly used by this rule to the Set
			allUsedNonterminals.addAll(usedNonterminals);

			for (String usedNonterminal: usedNonterminals) {
				// use recursion to extract every needed rule
				fillUsedNonterminals(allUsedNonterminals, usedNonterminal);
			}
		} else {
			sLog.warning("Could not find a rule for nonterminal: "+nonterminal);
		}
	}
}
